// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import PROTOCOL_CONFIG from './config/protocolConfig';
import Util from './Util';
import { DEVICE_ORIENTATION_ANGLES } from './config/commonConfig';

// 屏幕刷新频率一般为60fps
const MOVE_THROTTLE_DELAY = 16.6;
const ORIENTATION_PORTRAIT = 'PORTRAIT';
const ORIENTATION_REVERSE_PORTRAIT = 'REVERSE_PORTRAIT';
const ORIENTATION_LANDSCAPE = 'LANDSCAPE';
const ORIENTATION_REVERSE_LANDSCAPE = 'REVERSE_LANDSCAPE';
const KEYBOARD_ID_PREFIX = 'phoenix_keyboard_';
const ORIENTATION_DEGRESS = {
  PORTRAIT: 0,
  LANDSCAPE: 90,
  REVERSE_LANDSCAPE: -90,
  REVERSE_PORTRAIT: 180,
};
const ORIENTATION_ORIGIN = {
  PORTRAIT: 0,
  LANDSCAPE: 3,
  REVERSE_LANDSCAPE: 3,
  REVERSE_PORTRAIT: 2,
};
const ORIENTATION_ORIGIN_MOBILE = {
  PORTRAIT: 0,
  LANDSCAPE: 3,
  REVERSE_LANDSCAPE: 1,
  REVERSE_PORTRAIT: 2,
};
const KEYBOARD_MODE = {
  KEYBOARD_MAP: 'KEYBOARD_MAP',
  KEYBOARD_INPUT: 'KEYBOARD_INPUT',
};

export default class TouchHandler {
  constructor({
    player,
    isMobile,
    sendHandler,
    isDebug,
    autoRotate,
    inputId = '',
    containerId,
    fullscreenId,
  }) {
    // 离开画面前最后一次鼠标操作
    this.lastMouseAction = '';
    this.container = document.getElementById(containerId);
    this.fullscreenId = fullscreenId;
    // 监听触控鼠键事件的Dom
    this.displayDom = player;
    // 事件回调，可通过该函数获取模拟事件
    this.sendHandler = sendHandler;
    // 是否移动端
    this.isMobile = isMobile;
    // debug下，显示调测过程信息
    this.isDebug = isDebug;
    this.autoRotate = autoRotate;
    // 远端云手机信息
    this.phone = { ...PROTOCOL_CONFIG.DEFAULT_RESOLUTION };
    this.resolution = { ...PROTOCOL_CONFIG.DEFAULT_RESOLUTION };
    // 手机浏览器接入，使用浏览器可用宽高
    if (this.isMobile) {
      this.resolution = {
        width: window.innerWidth,
        height: window.innerHeight,
      };
    }

    // 云手机画面方向，默认竖屏，若启动竖屏，CAE不发送旋转msg
    this.orientation = ORIENTATION_PORTRAIT;
    // player显示内容区坐标相对contain的偏移
    this.displayBox = {
      top: 0,
      left: 0,
      ...PROTOCOL_CONFIG.DEFAULT_RESOLUTION,
    };
    // 本地播放画面相对远端手机大小的比例
    this.scale = {
      x: 1,
      y: 1,
    };
    this.keyboardMode = KEYBOARD_MODE.KEYBOARD_MAP;
    // 存储模拟数据
    this.simulationState = {
      mouse: {},
      touch: {},
    };
    // 多点触控不超过20个触点
    this.virtualTouchIds = new Uint8Array(20);
    this.mouseCode = {
      primary: 0,
      assist: 1,
      secondary: 2,
    };
    // 是否拖拽模式
    this.dragMode = true;
    this.util = new Util();
    this.touchListKey = 'changedTouches';
    // 存储用于定时触发move事件的定时器
    this.moveTimer = {};
    this.viewportOrientation = window.orientation;
    this.isApple = /(iPhone|iPad|iPod|iOS|Macintosh)/i.test(
      (navigator && navigator.userAgent) || '',
    );
    this.inputId = inputId;
  }

  start() {
    this.updateScale();

    this.resize();
    this.util.bind(window, 'resize', this.resize.bind(this));

    if (this.displayDom) {
      if (this.isMobile) {
        this.util.bind(
          this.displayDom,
          'touchstart',
          this.onTouchStart.bind(this),
        );
        this.util.bind(this.displayDom, 'touchend', this.onTouchEnd.bind(this));
        this.util.bind(
          this.displayDom,
          'touchmove',
          this.onTouchMove.bind(this),
        );
        this.util.bind(
          this.displayDom,
          'touchcancel',
          this.onTouchEnd.bind(this),
        );
      } else {
        this.util.bind(
          this.displayDom,
          'mousedown',
          this.onMouseDown.bind(this),
        );
        this.util.bind(this.displayDom, 'mouseup', this.onMouseUp.bind(this));
        this.util.bind(
          this.displayDom,
          'mousemove',
          this.util
            .throttle(this.onMouseMove.bind(this), MOVE_THROTTLE_DELAY, this)
            .bind(this),
        );
        this.util.bind(
          this.container,
          'mouseleave',
          this.onMouseLeave.bind(this),
        );

        this.util.bind(this.displayDom, 'contextmenu', () => false);
      }

      this.displayDom.focus();

      this.autoRotate &&
        this.util.bind(
          window,
          'orientationchange',
          this.viewportOrientationChange.bind(this),
        );
    }
  }

  viewportOrientationChange() {
    this.viewportOrientation = window.orientation;
  }

  /**
   * 更新方向
   * @param {string} orientation 方向
   **/
  updateOrientation(orientation) {
    this.orientation = orientation;
    this.resize();
  }

  updateResolution(width, height) {
    this.resolution = { width, height };
    this.resize();
  }

  updateKeyBoardMode(mode) {
    if (!KEYBOARD_MODE[mode]) {
      return;
    }
    this.keyboardMode = mode;
  }

  /**
   * 获取Android触控事件类型
   * @param {string} type H5触控事件类型
   * @param {object} touchRecords 已触发的触控鼠键记录
   * @returns {string} Android触控事件类型
   */
  getTouchAction(type, touchRecords) {
    const mouseStateNum =
      (touchRecords.mouse && Object.keys(touchRecords.mouse).length) || 0;
    const touchNum =
      (touchRecords.touch && Object.keys(touchRecords.touch).length) || 0;
    // 方向键合成一个vId，需要去重
    const keyboardNum = [
      ...new Set(
        (touchRecords.keyboard && Object.values(touchRecords.keyboard)) || [],
      ),
    ].length;
    const total = mouseStateNum + touchNum + keyboardNum;
    let prefix = '';
    if ((type === 'UP' && total) || (type === 'DOWN' && total > 1)) {
      prefix = 'POINTER_';
    }

    return prefix + type;
  }

  // 阻止原生事件
  preventDefault(event) {
    event.stopPropagation();
    event.preventDefault();
  }

  onTouchStart(event) {
    event = event || window.event;
    this.preventDefault(event);

    // TouchList是array like结构的对象，包含length属性。
    const touchList = event[this.touchListKey] || { length: 0 };
    for (let len = touchList.length, index = 0; index < len; index++) {
      const touch = touchList.item(index) || {};
      const existTouch = this.simulationState.touch[touch.identifier];
      if (existTouch) {
        this.sendTouchMsg(existTouch, 'UP');
        this.freeVirtualTouchId(existTouch.vId);
        delete this.simulationState.touch[touch.identifier];
      }

      touch.vId = this.allocVirtualTouchId();
      this.simulationState.touch[touch.identifier] = touch;
      const pos = this.getCurrentMousePos(touch);
      this.sendTouchMsg(
        {
          ...pos,
          vId: touch.vId,
        },
        this.getTouchAction('DOWN', this.simulationState),
      );
    }
  }

  onTouchMove(event) {
    event = event || window.event;
    this.preventDefault(event);
    // TouchList是array like结构的对象，包含length属性。
    const touchList = event[this.touchListKey] || { length: 0 };
    for (let len = touchList.length, index = 0; index < len; index++) {
      const touch = touchList.item(index) || {};
      const existTouch = this.simulationState.touch[touch.identifier];
      const pos = this.getCurrentMousePos(touch);
      this.sendTouchMsg(
        {
          ...pos,
          vId: (existTouch && existTouch.vId) || 0,
        },
        'MOVE',
      );
      if (
        pos.x > this.displayBox.width ||
        pos.x < 0 ||
        pos.y < 0 ||
        pos.y > this.displayBox.height
      ) {
        this.clearTouchAndMouse();
      }
    }
  }

  onTouchEnd(event) {
    event = event || window.event;
    this.preventDefault(event);
    // TouchList是array like结构的对象，包含length属性。
    const touchList = event[this.touchListKey] || { length: 0 };
    for (let len = touchList.length, index = 0; index < len; index++) {
      const touch = touchList.item(index) || {};
      const existTouch = this.simulationState.touch[touch.identifier];
      if (existTouch) {
        this.freeVirtualTouchId(existTouch.vId);
        delete this.simulationState.touch[touch.identifier];
      }

      const pos = this.getCurrentMousePos(touch);
      this.sendTouchMsg(
        {
          ...pos,
          vId: (existTouch && existTouch.vId) || 0,
        },
        this.getTouchAction('UP', this.simulationState),
      );
    }
  }

  _mouseAction(event, action) {
    this.lastMouseAction = action;
    const mouseCode = event.button;
    const pos = this.getCurrentMousePos(event);
    let mouseState = this.simulationState.mouse;
    let vId = mouseState[mouseCode];
    if (action === 'UP') {
      this.freeVirtualTouchId(vId);
      delete mouseState[mouseCode];
    } else {
      vId = this.allocVirtualTouchId();
      mouseState[mouseCode] = vId;
    }
    this.sendTouchMsg(
      {
        ...pos,
        vId,
      },
      this.getTouchAction(action, this.simulationState),
    );
  }

  onMouseDown(event) {
    event = event || window.event;
    this.container.focus()
    this.preventDefault(event);
    if (event.button === this.mouseCode.assist) {
      this.dragMode = !this.dragMode;
      if (!this.dragMode) {
        this._mouseAction(event, 'DOWN');
      } else {
        this._mouseAction(event, 'UP');
      }
    } else {
      this._mouseAction(event, 'DOWN');
    }
  }

  onMouseMove(event) {
    event = event || window.event;
    this.preventDefault(event);
    const mouseCodes = Object.keys(this.simulationState.mouse);
    const mouseCode = mouseCodes.find(
      code => code in this.simulationState.mouse,
    );
    if (mouseCode !== undefined) {
      let pos = this.getCurrentMousePos(event);
      this.sendTouchMsg(
        {
          ...pos,
          vId: this.simulationState.mouse[mouseCode],
        },
        'MOVE',
      );
    }
  }

  onMouseLeave() {
    if (this.lastMouseAction === 'DOWN') {
      this.lastMouseAction = 'UP';
      this.clearTouchAndMouse();
    }
  }

  onMouseUp(event) {
    event = event || window.event;
    this.preventDefault(event);
    if (event.button !== this.mouseCode.assist) {
      this._mouseAction(event, 'UP');
    }
  }

  isSameOrientation() {
    if (!this.isMobile) {
      return !['LANDSCAPE', ORIENTATION_REVERSE_LANDSCAPE].includes(
        this.orientation,
      );
    }

    // 若云手机画面横屏，当前认为云手机方向为逆时针旋转90度。
    // 故若不自动旋转，云手机画面竖屏时，浏览器和云手机画面方向一致；
    // 若自动旋转，1）真机方向竖屏，则云手机会旋转至和浏览器一个方向，2）若真机左旋转或又旋转至横屏，则云手机画面会旋转至浏览器逆时针90度的位置
    return (
      (!this.autoRotate && this.orientation === ORIENTATION_PORTRAIT) ||
      (this.autoRotate &&
        (this.viewportOrientation === ORIENTATION_DEGRESS.PORTRAIT ||
          this.viewportOrientation === ORIENTATION_DEGRESS.REVERSE_PORTRAIT))
    );
  }

  getScale(current, target) {
    if (this.isSameOrientation()) {
      return {
        x: current.width ? target.width / current.width : 1,
        y: current.height ? target.height / current.height : 1,
      };
    }

    return {
      x: current.height ? target.width / current.height : 1,
      y: current.width ? target.height / current.width : 1,
    };
  }

  // 更新缩放比例
  updateScale() {
    this.scale = this.getScale(this.displayBox, this.phone);
  }

  // 更新player显示区域信息
  updateContainBox(container) {
    const scaleObj = this.getScale(container, this.resolution);
    const scale = Math.max(scaleObj.x, scaleObj.y);
    if (this.isSameOrientation()) {
      this.displayBox.width = this.resolution.width / scale;
      this.displayBox.height = this.resolution.height / scale;
    } else {
      this.displayBox.width = this.resolution.height / scale;
      this.displayBox.height = this.resolution.width / scale;
    }

    this.displayBox.left = (container.width - this.displayBox.width) >> 1;
    this.displayBox.top = (container.height - this.displayBox.height) >> 1;
  }

  // 界面resize，更新显示区域信息和键盘位置信息，以及缩放比例等和大小位置相关的信息
  resize(width, height) {
    if (!this.displayDom) {
      return;
    }

    if (width && height) {
      this.displayBox.width = width;
      this.displayBox.height = height;
    } else {
      // 旋转后，通过getClientRects拿到的宽高为旋转后宽高，所见即所得，如（宽，高）=（200px, 100px）的块旋转90度后获取到（宽，高）=（100px, 200px）
      const displayRects = this.displayDom.getClientRects();
      this.displayBox.width =
        displayRects && displayRects[0] && displayRects[0].width;
      this.displayBox.height =
        displayRects && displayRects[0] && displayRects[0].height;
    }

    this.displayBox.top = 0;
    this.displayBox.left = 0;
    // 若player等比例填充，则实际内容区的宽高并非获取到的宽高
    if (this.displayDom.style.objectFit === 'contain') {
      this.updateContainBox({ ...this.displayBox });
    }

    this.updateScale();
  }

  /**
   * 计算触控点相对于云手机画面左上角的位置信息
   * @param {object} eventTouch 触控事件
   * @return {object} 位置
   */
  getCurrentMousePos(eventTouch) {
    const docRects = document.documentElement.getClientRects()[0];
    const displayRects = this.displayDom.getClientRects()[0];

    let x = eventTouch.pageX - displayRects.left - this.displayBox.left;
    let y =
      eventTouch.pageY - displayRects.top - this.displayBox.top + docRects.top;
    if (this.orientation !== ORIENTATION_REVERSE_LANDSCAPE) {
      return {
        x: x,
        y: y,
      };
    } else {
      return {
        x: this.isMobile ? x : this.displayBox.width - x,
        y: this.isMobile ? y : this.displayBox.height - y,
      };
    }
  }

  // 分配虚拟ID
  allocVirtualTouchId() {
    const index = this.virtualTouchIds.indexOf(0);
    if (index === -1) {
      this.virtualTouchIds.map(() => 0);
      this.virtualTouchIds[0] = 1;
      return 0;
    }

    this.virtualTouchIds[index] = 1;
    return index;
  }

  // 释放虚拟ID
  freeVirtualTouchId(virtualId) {
    this.virtualTouchIds[virtualId] = 0;
  }

  getMsgBuf(data) {
    if (!this.sendHandler) {
      return;
    }

    const PACKAGE_HEADER_LENGTH = 8;
    const TOUCH_MSG_BODY_LENGTH = 17;
    const TOUCH_ACTION = 6;

    let buf = new Uint8Array(PACKAGE_HEADER_LENGTH + TOUCH_MSG_BODY_LENGTH);
    buf[0] = 90;
    buf[1] = 90;
    buf[2] = 180 + TOUCH_ACTION;
    buf[3] = TOUCH_ACTION;
    buf[7] = TOUCH_MSG_BODY_LENGTH;
    buf[8] = data.id;
    buf[9] = data.action;
    buf[10] = (data.x >> 8) & 0xff;
    buf[11] = data.x & 0xff;
    buf[12] = (data.y >> 8) & 0xff;
    buf[13] = data.y & 0xff;
    buf[14] = (data.pressure >> 8) & 0xff;
    buf[15] = data.pressure & 0xff;
    buf[16] = (data.time & 0xff000000) >> 24;
    buf[17] = (data.time & 0x00ff0000) >> 16;
    buf[18] = (data.time & 0x0000ff00) >> 8;
    buf[19] = data.time & 0x000000ff;
    buf[20] = data.orientation;
    buf[21] = (data.height >> 8) & 0xff;
    buf[22] = data.height & 0xff;
    buf[23] = (data.width >> 8) & 0xff;
    buf[24] = data.width & 0xff;
    return buf;
  }

  getKeyCodeEventMsgBuf(data) {
    if (!this.sendHandler) {
      return;
    }

    const PACKAGE_HEADER_LENGTH = 8;
    const TOUCH_MSG_BODY_LENGTH = 4;
    const TOUCH_ACTION = 15;

    let buf = new Uint8Array(PACKAGE_HEADER_LENGTH + TOUCH_MSG_BODY_LENGTH);
    buf[0] = 90;
    buf[1] = 90;
    buf[2] = 180 + TOUCH_ACTION;
    buf[3] = TOUCH_ACTION;
    buf[7] = TOUCH_MSG_BODY_LENGTH;
    buf[8] = (data.keycode >> 8) & 0xff;
    buf[9] = data.keycode & 0xff;
    buf[10] = (data.action >> 8) & 0xff;
    buf[11] = data.action & 0xff;
    return buf;
  }

  /**
   * 通过回调，发送模拟的触控事件
   * touchstart : 触摸开始（手指放在触摸屏上）
   * touchmove : 拖动（手指在触摸屏上移动）
   * touchend : 触摸结束（手指从触摸屏上移开）
   * touchenter ：移动的手指进入一个dom元素。
   * touchleave ：移动的手指离开一个dom元素。
   */
  sendTouchMsg(touch, action) {
    // 触控点不在player区域，不发送指令
    if (
      touch.x > this.displayBox.width ||
      touch.x < 0 ||
      touch.y < 0 ||
      touch.y > this.displayBox.height
    ) {
      return;
    }
    let finalOrientation;
    if (this.isMobile) {
      if (document.getElementById(this.fullscreenId).offsetWidth <= 720) {
        finalOrientation = 0;
      } else {
        finalOrientation = ORIENTATION_ORIGIN_MOBILE[this.orientation];
      }
    } else {
      finalOrientation = ORIENTATION_ORIGIN[this.orientation];
    }
    const msg = {
      ...touch,
      action: PROTOCOL_CONFIG.ACTIONS_TYPE[action],
      id: touch.vId,
      pressure: 129,
      time: -1,
      orientation: finalOrientation,
      height: this.displayBox.height,
      width: this.displayBox.width,
    };
    const msgBuf = this.getMsgBuf(msg);
    this.sendHandler(msgBuf);
  }

  sendKeyCodeEventMsg(action, keycode) {
    const msg = {
      keycode,
      action,
    };
    const msgBuf = this.getKeyCodeEventMsgBuf(msg);
    this.sendHandler(msgBuf);
  }

  clearTouchAndMouse() {
    this.freeVirtualTouchId(0);
    this.simulationState.touch = {};
    this.simulationState.mouse = {};
    this.sendTouchMsg(
      {
        x: 0,
        y: 0,
        vId: 0,
      },
      'UP',
    );
  }

  destroy() {
    this.util.unbind(window, 'resize');
    this.util.unbind(window, 'keydown');
    this.util.unbind(window, 'keyup');
    this.util.unbind(this.displayDom);
    this.autoRotate && this.util.unbind(window, 'orientationchange');
    Object.keys(this.moveTimer).forEach(key => {
      this.moveTimer[key] && clearInterval(this.moveTimer[key]);
    });

    // 删除增加的keyboard Dom
    if (this.displayDom && this.displayDom.parentNode) {
      const childrens = this.displayDom.parentNode.children || [];
      const keys =
        (this.displayKeyboardMap && Object.keys(this.displayKeyboardMap)) || [];
      let ids = keys.map(key => `${KEYBOARD_ID_PREFIX}${key}`);
      ids.push('posTracker');
      let removeList = [];
      for (let i = 0, len = childrens.length; i < len; i++) {
        if (ids.indexOf(childrens[i].id) > -1) {
          removeList.push(childrens[i]);
        }
      }

      removeList.forEach(item => this.displayDom.parentNode.removeChild(item));
    }

    this.displayDom = null;
  }
}
